using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using Nojin.Netwwork.Download;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Diagnostics;
using Nojin.Deployment.UpdaterServer;
using System.Configuration;
using System.Net;

namespace Nojin.Deployment
{

    /// <summary>
    /// This class checks for new version availability and creates the process object for downloaing and installing
    /// the new version.
    /// </summary>
    /// <remarks>
    /// To check for new version availaibility call the <seealso cref="CheckForUpdate"/> or <seealso cref="CheckForUpdateAsync"/>.
    /// To create the process call the <seealso cref="CreateProcessAsync"/> method.
    /// To get the result of asynchron methods handle the appropriate events.    
    /// 
    /// To get new version information this method relys on service which it's url should be specified in configuration file.
    /// The key name for autoupdate service uril is "autoUpdateServiceUri". 
    /// <code>
    /// <appSettings>
    ///     <add key="autoUpdateServiceUri" value="http://www.nonexistingcompany.com/subjectApplicationName/UpdateService.asmx" />
    /// </appSettings>
    /// </code>
    /// </remarks>
    public sealed class ApplicationDeployment
    {

        #region Constructors

        /// <summary>
        /// Constructs an instance of ApplicationDeployment.        
        /// </summary>
        /// <param name="currentVersion">Version of subject application.</param>
        public ApplicationDeployment(Version currentVersion)
        {
            this.currentVersion = currentVersion;
        }


        /// <summary>
        /// Constructs an instance of AplicationDeployment.
        /// This constructor will automatically use current directory name as current version.
        /// If you want to specify current version manually use the constructor which accepts 
        /// Version parameter.
        /// </summary>
        public ApplicationDeployment()
        {
            //string applicationFolder = new DirectoryInfo(Environment.CurrentDirectory).Name;         
            //currentVersion = new Version(applicationFolder);

            string currentVersionText = LauncherHelperMethods.GetLauncherConfig().AppSettings.Settings[Constants.TargetVersionConfigKey].Value;
            currentVersion = new Version(currentVersionText);

            // If we want to use assemblies'version, use below codes to obtain current version.
            //currentVersion = Assembly.GetEntryAssembly().GetName().Version;
        }

        #endregion

        #region Fields
        Version currentVersion;
        UpdateInfo updateInfo; // this is a temporary varaible to pass value between two methods:CreateProcessAsync and CreateProcessAsync_minfestDownloader_DownloadCompleted.
        string autoUpdateUri;
        #endregion

        #region Properties
        public string AutoUpdateUri
        {
            get
            {
                if (string.IsNullOrEmpty(autoUpdateUri))
                {
                    return ConfigurationManager.AppSettings[Constants.AutoUpdateServicUriCongigKey];
                }
                else
                {
                    return autoUpdateUri;
                }
            }
            set
            {
                autoUpdateUri = value;
            }
        }
        #endregion

        #region Methods

        /// <summary>
        /// Using the configraution specified web service checks for new version availability.
        /// Web service uri will be specified with the application settings with key "autoUpdateServiceUri".
        /// </summary>
        /// <returns>If exists new version available return appropriate UpdateInfo object. otherwise returns null.</returns>
        public UpdateInfo CheckForUpdate()
        {
            UpdaterServer.UpdateInfoProxy infoProxy;
            try
            {                
                UpdateService updateService = new UpdateService();                
                updateService.Url = ConfigurationManager.AppSettings[Constants.AutoUpdateServicUriCongigKey];
                infoProxy = updateService.GetNewReleaseInfo(currentVersion.Major, currentVersion.Minor, currentVersion.Build, currentVersion.Revision);
            }
            catch (WebException)// Unable to connect to the remote server
            {
                infoProxy = null;
            }

            if (infoProxy != null)
            {
                UpdateInfo updateInfo = new UpdateInfo(infoProxy.Name, infoProxy.Description, new Version(infoProxy.Version), infoProxy.ReleaseDate.Value, infoProxy.PackageSize, infoProxy.Arbitary, infoProxy.ManifestPath, infoProxy.ChangeLogFilePath, infoProxy.SilentUpdate);
                return updateInfo;
            }
            else
                return null;
        }

        /// <summary>
        /// This is a asynchronized version of <seealso cref="CheckForUpdateMethod"/>.
        /// To get results handle the <seealso cref="CheckForUpdateCompleted"/> event.
        /// </summary>
        public void CheckForUpdateAsync()
        {
            UpdateService updateService = new UpdateService();
            updateService.Url = ConfigurationManager.AppSettings[Constants.AutoUpdateServicUriCongigKey];
            updateService.GetNewReleaseInfoCompleted += new GetNewReleaseInfoCompletedEventHandler(updateService_GetNewReleaseInfoCompleted);
            updateService.GetNewReleaseInfoAsync(currentVersion.Major, currentVersion.Minor, currentVersion.Build, currentVersion.Revision);
        }

        void updateService_GetNewReleaseInfoCompleted(object sender, GetNewReleaseInfoCompletedEventArgs e)
        {
            UpdateInfo updateInfo;

            if (e.Error != null)
            {
                OnCheckForUpdateException(new ExceptionOccuredEventArgs(e.Error));
            }

            if (e.Result == null)
                updateInfo = null;
            else
            {
                updateInfo = new UpdateInfo(e.Result.Name, e.Result.Description, new Version(e.Result.Version), e.Result.ReleaseDate.Value, e.Result.PackageSize, e.Result.Arbitary, e.Result.ManifestPath, e.Result.ChangeLogFilePath,e.Result.SilentUpdate);
            }
            OnCheckForUpdateCompleted(new CheckForUpdateCompletedEventArgs(updateInfo));
        }

        /// <summary>
        /// Creates an instance of <seealso cref="ApplicationDeploymentProcess"/> for downloading and installing
        /// the new version specified with updateInfo.
        /// To get results you must handle <seealso cref="ProcessCreated"/> event.
        /// Using the result process object you could run the process to perform the installation.
        /// also you may control the process by handling it events.
        /// for more information see <seealso cref="ApplicationDeploymentProcess"/>.
        /// </summary>
        /// <param name="updateInfo"></param>
        public void CreateProcessAsync(UpdateInfo updateInfo)
        {
            this.updateInfo = updateInfo;
            FileDownloader manifestDownloader = new FileDownloader();
            manifestDownloader.DownloadCompleted += new EventHandler<FileDownloadCompletedEventArgs>(CreateProcessAsync_minfestDownloader_DownloadCompleted);
            manifestDownloader.Download(updateInfo.ManifestPath, AppDomain.CurrentDomain.BaseDirectory);
        }

        void CreateProcessAsync_minfestDownloader_DownloadCompleted(object sender, FileDownloadCompletedEventArgs e)
        {            
            string manifestFileName = Path.GetFileName(updateInfo.ManifestPath);
            string manifestLocalFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, manifestFileName);
            if (File.Exists(manifestLocalFilePath))
            {
                DeploymentManifest manifest = DeploymentManifest.Load(manifestLocalFilePath);
                ApplicationDeploymentProcess process = new ApplicationDeploymentProcess("NewVersion",updateInfo,manifest);
                OnProcessCreated(new ProcessCreatedEventArgs(process));
            }
            else
            {
                throw new Exception("Unable to find manifest, possibly there is a problem in downloading it. retry download.");
            }
        }

        #endregion

        #region Events

        private event EventHandler<CheckForUpdateCompletedEventArgs> checkForUpdateCompleted;
        /// <summary>
        /// In response to calling the method <seealso cref="CheckForUpdateAsync"/>, it results
        /// will be announced by this event.
        /// </summary>
        public event EventHandler<CheckForUpdateCompletedEventArgs> CheckForUpdateCompleted
        {
            add
            {
                checkForUpdateCompleted += value;
            }
            remove
            {
                checkForUpdateCompleted -= value;
            }
        }
        private void OnCheckForUpdateCompleted(CheckForUpdateCompletedEventArgs e)
        {
            if (checkForUpdateCompleted != null)
                checkForUpdateCompleted(this, e);
        }

        private event EventHandler<ExceptionOccuredEventArgs> checkForUpdateException;
        public event EventHandler<ExceptionOccuredEventArgs> CheckForUpdateException
        {
            add
            {
                checkForUpdateException += value;
            }
            remove
            {
                checkForUpdateException -= value;
            }
        }
        private void OnCheckForUpdateException(ExceptionOccuredEventArgs e)
        {
            if (checkForUpdateException != null)
                checkForUpdateException(this, e);
        }

        private event EventHandler<ProcessCreatedEventArgs> processCreated;
        /// <summary>
        /// In response to calling the method <seealso cref="CreateProcessAsync"/>, it results
        /// will be announced by this event.
        /// </summary>
        public event EventHandler<ProcessCreatedEventArgs> ProcessCreated
        {
            add
            {
                processCreated += value;
            }
            remove
            {
                processCreated -= value;
            }
        }
        private void OnProcessCreated(ProcessCreatedEventArgs e)
        {
            if (processCreated != null)
                processCreated(this, e);
        }
        #endregion

    }

    /// <summary>
    /// Encapsulates information for <seealso cref="ApplicationDeployment.ProcessCreated"/> event.
    /// </summary>
    public class ProcessCreatedEventArgs : EventArgs
    {
        #region Fields

        ApplicationDeploymentProcess process;
        
        #endregion

        #region Constructors
        public ProcessCreatedEventArgs(ApplicationDeploymentProcess process)
        {
            this.process = process;
        } 
        #endregion

        #region Properties
        /// <summary>
        /// The process object created for downloaing and installing new version.
        /// You could call the Run method, and to control the process handle it events.
        /// </summary>
        public ApplicationDeploymentProcess Process
        {
            get { return process; }
            set { process = value; }
        } 
        #endregion
    }

    /// <summary>
    /// Encapsulates information for <seealso cref="ApplicationDeployment.CheckForUpdateCompleted"/> event.
    /// </summary>
    public class CheckForUpdateCompletedEventArgs : EventArgs
    {
        #region Fields
        UpdateInfo updateInfo;        
        #endregion

        #region Constructors
        public CheckForUpdateCompletedEventArgs(UpdateInfo updateInfo)
        {
            this.updateInfo = updateInfo;
        }
        #endregion

        #region Properties
        /// <summary>
        /// The result UpdateInfo.
        /// It may be null if there is no new update available for subject application.
        /// </summary>
        public UpdateInfo UpdateInfo
        {
            get
            {
                return updateInfo;
            }
        }
        #endregion
    }

    public class ExceptionOccuredEventArgs : EventArgs
    {
        #region Fields
        Exception exception;        
        #endregion

        #region Constructors
        public ExceptionOccuredEventArgs(Exception exception)
        {
            this.exception = exception;
        }
        #endregion

        #region Properties
        public Exception Exception
        {
            get
            {
                return exception;
            }
        }
        #endregion
    }

}
